package bancosys.tec.persist.spring;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import jmine.tec.utils.debug.impl.DebugRuntimeFactory;

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.springframework.orm.hibernate3.SessionFactoryUtils;

import bancosys.tec.persist.authorization.AuthorizationManager;
import bancosys.tec.persist.hibernate.PartitionSessionFactory;
import bancosys.tec.persist.hibernate.impl.annotations.AnnotationPartitionSessionFactory;
import bancosys.tec.persist.hibernate.page.PagingService;

/**
 * @author piercio
 */
public class PartitionAnnotationSessionFactoryBean extends LocalSessionFactoryBean {

    private PagingService pagingService;

    private SessionFactory pureSessionFactory;

    private Configuration pureSessionFactoryConfiguration;

    private AuthorizationManager boAuthorizationManager;

    private FlushModeEnum flushMode;

    /**
     * Define o authoriazion manager que será utilizado nas sessões.
     * 
     * @param boAuthorizationManager o authoriazion manager que será utilizado.
     */
    public void setBoAuthorizationManager(AuthorizationManager boAuthorizationManager) {
        this.boAuthorizationManager = boAuthorizationManager;
    }

    /**
     * Devolve a session factory puro de hibernate.
     * 
     * @return a session factory puro de hibernate
     */
    public SessionFactory getPureSessionFactory() {
        return this.pureSessionFactory;
    }

    /**
     * Define o session factory puro de hibernate.
     * 
     * @param bean a session factory que cria uma {@link org.hibernate.SessionFactory} de hibernate.
     */
    public void setPureSessionFactoryBean(LocalSessionFactoryBean bean) {
        this.pureSessionFactory = (SessionFactory) bean.getObject();
        this.pureSessionFactoryConfiguration = bean.getConfiguration();
    }

    /**
     * @param sessionFactory the session factory
     * @param cfg the cfg
     */
    public void configureExternal(SessionFactory sessionFactory, Configuration cfg) {
        this.pureSessionFactory = sessionFactory;
        this.pureSessionFactoryConfiguration = cfg;
    }

    /**
     * Define o modo de flush de sessão do hibernate.
     * 
     * @param flushMode o modo de flush de sessão do hibernate que seráusado.
     */
    public void setFlushMode(FlushModeEnum flushMode) {
        this.flushMode = flushMode;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected SessionFactory newSessionFactory(Configuration config) {
        SessionFactory sessionFactory;
        if (this.flushMode != null) {
            sessionFactory =
                    new AnnotationPartitionSessionFactory(config, this.pureSessionFactory, this.boAuthorizationManager,
                            this.flushMode.getFlushMode());
        } else {
            sessionFactory = new AnnotationPartitionSessionFactory(config, this.pureSessionFactory, this.boAuthorizationManager);
        }
        sessionFactory.getStatistics().setStatisticsEnabled(DebugRuntimeFactory.ENABLED);
        return sessionFactory;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected SessionFactory buildSessionFactory() throws Exception {
        return this.newSessionFactory(this.newConfiguration());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected SessionFactory wrapSessionFactoryIfNecessary(SessionFactory target) {
        if (this.isExposeTransactionAwareSessionFactory()) {
            PartitionSessionFactory sessionFactory =
                    (PartitionSessionFactory) Proxy.newProxyInstance(SessionFactory.class.getClassLoader(), target.getClass()
                            .getInterfaces(), new PartitionTransactionAwareInvocationHandler(target));
            sessionFactory.setPagingService(this.pagingService);
            return sessionFactory;
        } else {
            return target;
        }
    }

    /**
     * Devolve o serviço de paginação.
     * 
     * @return o serviço de paginação
     */
    public PagingService getPagingService() {
        return this.pagingService;
    }

    /**
     * Define o serviço de paginação.
     * 
     * @param pagingService o serviço de paginação a ser utilizado.
     */
    public void setPagingService(PagingService pagingService) {
        this.pagingService = pagingService;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Configuration newConfiguration() {
        return this.pureSessionFactoryConfiguration;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void postProcessConfiguration(Configuration config) {
        super.postProcessConfiguration(config);
    }

    /**
     * Invocation handler that delegates <code>getCurrentSession</code> calls to SessionFactoryUtils, for being aware of thread-bound
     * transactions.
     */
    private static class PartitionTransactionAwareInvocationHandler implements InvocationHandler {

        private final SessionFactory target;

        /**
         * Construtor.
         * 
         * @param target a session factory que será utilizada.
         */
        public PartitionTransactionAwareInvocationHandler(SessionFactory target) {
            this.target = target;
        }

        /**
         * Exetuca um método na session factory.
         * 
         * @param proxy o objeto.
         * @param method o método.
         * @param args a lista de parâmetros do método.
         * @return o resultado da execução do método.
         * @throws Throwable se ocorrer algum error ao executar o método.
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Invocation on SessionFactory/SessionFactoryImplementor interface coming
            // in...

            if (method.getName().equals("getCurrentSession")) {
                // Handle getCurrentSession method: return transactional Session, if
                // any.
                try {
                    return SessionFactoryUtils.doGetSession((SessionFactory) proxy, false);
                } catch (IllegalStateException ex) {
                    throw new HibernateException(ex.getMessage());
                }
            } else if (method.getName().equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
            } else if (method.getName().equals("hashCode")) {
                // Use hashCode of SessionFactory proxy.
                return Integer.valueOf(this.hashCode());
            }

            // Invoke method on target SessionFactory.
            try {
                return method.invoke(this.target, args);
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }
}